<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <script>
        (function (Sweep) {
            const W = 20, H = 20, SIZE = 20, difficulty = .1
            const WIDTH = W * SIZE, HEIGHT = H * SIZE
            const { points, openPoint } = new Sweep(W, H, difficulty)
            const canvas = document.createElement("canvas")
            canvas.width = WIDTH
            canvas.height = HEIGHT
            const ctx = canvas.getContext("2d")
            ctx.textAlign = 'center'
            ctx.textBaseline = 'middle'

            let scale = 1
            let delta = 1
            let offset = {
                x: 0,
                y: 0
            }
            addEventListener('mousewheel', e => {
                delta = 1 + e.deltaY / 1000;
                scale *= delta
                render();
                e.preventDefault();
            })
            const render = function () {
                ctx.clearRect(0, 0, WIDTH / scale, HEIGHT / scale)
                ctx.scale(delta, delta)
                for (let x = 0; x < W; x++) {
                    let line = points[x]
                    for (let y = 0; y < H; y++) {
                        const { n, isMine, isOpen, flag } = line[y]
                        if (flag) {
                            ctx.fillStyle = 'green'
                            ctx.fillText('✅', (x + .5) * SIZE, (y + .5) * SIZE)
                        } else if (isOpen) {
                            ctx.fillStyle = '#fff'
                            ctx.fillRect(x * W, y * H, SIZE, SIZE)
                            if (n > 0) {
                                ctx.fillStyle = ['#fff', '#333', 'green', 'yellow', 'orange', '#c00', '#f00', '#f00'][n]
                                ctx.fillText(n, (x + .5) * SIZE, (y + .5) * SIZE)
                            }
                            if (isMine) {
                                ctx.fillStyle = 'red'
                                ctx.fillText('雷', (x + .5) * SIZE, (y + .5) * SIZE)
                            }
                        } else {
                            ctx.fillStyle = '#d2d2d2'
                            ctx.fillRect(x * W, y * H, SIZE, SIZE)
                        }
                        ctx.strokeStyle = '#ccc'
                        ctx.strokeRect(x * W, y * H, SIZE, SIZE)
                    }
                }
            }
            let clicking
            canvas.addEventListener('click', function (e) {
                const { offsetX, offsetY } = e
                openPoint(offsetX / SIZE / scale | 0, offsetY / SIZE / scale | 0)
                render();
                if (clicking) {
                    const eve = new CustomEvent('dblclick')
                    Object.assign(eve, { offsetX, offsetY })
                    canvas.dispatchEvent(eve)
                }
                clicking = true
                setTimeout(function () {
                    clicking = false
                }, 300);
            })
            canvas.addEventListener('dblclick', function (e) {
                const { offsetX, offsetY } = e
                const x = offsetX / SIZE / scale | 0
                const y = offsetY / SIZE / scale | 0
                const a = points;
                [
                    a[x - 1] && a[x - 1][y - 1],
                    a[x - 1] && a[x - 1][y],
                    a[x - 1] && a[x - 1][y + 1],
                    a[x][y - 1],
                    a[x][y + 1],
                    a[x + 1] && a[x + 1][y - 1],
                    a[x + 1] && a[x + 1][y],
                    a[x + 1] && a[x + 1][y + 1]
                ].map(p => {
                    if (p && !p.flag) {
                        openPoint(p.x, p.y)
                    }
                })
                render()
            })
            canvas.addEventListener('contextmenu', function (e) {
                e.preventDefault()
                const { offsetX, offsetY } = e
                const point = points[offsetX / SIZE / scale | 0][offsetY / SIZE / scale | 0]
                point.flag = !point.flag
                render();
            })
            document.body.appendChild(canvas)
            render();
        })(function () {
            class Point {
                constructor(x, y, isMine) {
                    this.x = x
                    this.y = y
                    this.isMine = !!isMine
                    this.n = 0
                    this.isOpen = false
                    this.flag = false
                }
            }
            class Sweep {
                constructor(W, H, difficulty) {
                    this.W = W
                    this.H = H
                    this.dt = difficulty || .1
                    this.openPoint = this.openPoint.bind(this)
                    this.getNum = this.getNum.bind(this)
                    this._init()
                }
                openPoint(x, y) {
                    const { points, pointList, openPoint } = this
                    const a = points
                    const p = a[x] && a[x][y]
                    this.beginTime = this.beginTime || Date.now()
                    if (p && !p.isOpen) {
                        p.isOpen = true
                        if (p.isMine) {
                            setTimeout(function () {
                                alert('game over!')
                                window.location.reload()
                            }, 0);
                        } else if (!pointList.find((pt) => pt.isMine === pt.isOpen)) {
                            alert(`success! \n time: ${(Date.now() - this.beginTime) / 1000 | 0}s`)
                        } else if (!p.isMine && !p.n) {
                            openPoint(x - 1, y)
                            openPoint(x + 1, y)
                            openPoint(x, y - 1)
                            openPoint(x, y + 1)
                        }
                    }
                }
                getNum(p, a) {
                    const { x, y } = p
                    if (!p.isMine) {
                        p.n = [
                            a[x - 1] && a[x - 1][y - 1],
                            a[x - 1] && a[x - 1][y],
                            a[x - 1] && a[x - 1][y + 1],
                            a[x][y - 1],
                            a[x][y + 1],
                            a[x + 1] && a[x + 1][y - 1],
                            a[x + 1] && a[x + 1][y],
                            a[x + 1] && a[x + 1][y + 1]
                        ].filter(m => m && m.isMine).length
                    }
                }
                _init() {
                    const { W, H, dt, getNum } = this
                    let points = []
                    for (let x = 0; x < W; x++) {
                        let line = []
                        for (let y = 0; y < H; y++) {
                            line.push(new Point(x, y, Math.random() < dt))
                        }
                        points.push(line)
                    }
                    for (let x = 0; x < W; x++) {
                        let line = points[x]
                        for (let y = 0; y < H; y++) {
                            getNum(line[y], points)
                        }
                    }
                    this.pointList = points.reduce((a, line) => a.concat(line), [])
                    this.points = points
                }
            }
            return Sweep
        }())
    </script>
</body>
</html>